//
// Created by Gao Shihao on 2023/8/31.
//

#ifndef PLC2LLVM_DURINGTYPE_H
#define PLC2LLVM_DURINGTYPE_H
#include <plc2llvm/TypeSystem/BasicType.h>

namespace plcst {

    template <TypeKind Kind, int size>
    class DuringType: public BasicType {

    public:
        explicit DuringType(std::string&& name): BasicType( std::move(name)), initValue(0) {}
        DuringType(std::string&& name, i64 i): BasicType( std::move(name)), initValue(i) {}

        explicit DuringType(DuringType<Kind, size>* another) : BasicType(another) { }

        [[nodiscard]] i64 getInitValue() const;

        [[nodiscard]] i64 convert_to_64bits() const;

        [[nodiscard]] TypeKind getTypeKind() const override;

        [[nodiscard]] int getNBits() const override;

        virtual TypeMsg typeSynthesisForBinaryOperator(ExpressionOperator op, std::shared_ptr<Type> another) override{
            return {nullptr, 2}; 
        }

        virtual TypeMsg typeSynthesisForUnaryOperator(ExpressionOperator op) override {
            return {nullptr, 2};
        }

    private:
        i64 initValue;
        static inline int nbits = size;
        static inline TypeKind kind = Kind;

    public:
        virtual TypeMsg getLargerType(std::shared_ptr<Type> another) override {
            auto thisShared = ScopeManager::getScopeManager().getGlobalScope()->find<Type>(this->getTypeName());

            auto anotherTypeKind = another->getTypeKind();
            if(this->getTypeKind() == anotherTypeKind){ // this = another
                return {thisShared, 0};
            }

            auto typeMachine = TypeMachine::getTypeMachine();
            if(!typeMachine.isDuringType(anotherTypeKind)){ // another is not DURING type
                return {nullptr, 2};
            }
            auto thisNBit = this->getNBits();
            auto anotherDetail = std::dynamic_pointer_cast<BasicType>(another);
            auto anotherNBit = anotherDetail->getNBits();
            if(thisNBit > anotherNBit){// return larger one
                return {thisShared, 0};
            }else{
                return {another, 0};
            }
        }

        virtual llvm::Value* castTo(std::shared_ptr<Type> destTy, llvm::Value* src, llvm::IRBuilder<>* builder) override {
            auto anotherTypeKind = destTy->getTypeKind();
            if(this->getTypeKind() == anotherTypeKind){
                return src;
            }
            TypeMachine& typeMachine = TypeMachine::getTypeMachine();
            if(typeMachine.isDuringType(anotherTypeKind)){
                auto thisNBit = this->getNBits();
                auto anotherDetail = std::dynamic_pointer_cast<BasicType>(destTy);
                auto anotherNBit = anotherDetail->getNBits();
                if(thisNBit > anotherNBit){// return larger one
                    return builder->CreateTrunc(src, destTy->llvmty);
                }else{
                    return builder->CreateSExt(src, destTy->llvmty);
                }
            }else{
                throw SemanticError("bad date time cast");
                return nullptr;
            }
        }

        virtual std::shared_ptr<Type> clone() override {
            return std::make_shared<DuringType<Kind, size>>(this);
        }
    };

    template<TypeKind Kind, int size>
    TypeKind DuringType<Kind, size>::getTypeKind() const {
        return kind;
    }

    template<TypeKind Kind, int size>
    i64 DuringType<Kind, size>::convert_to_64bits() const {
        return 0;
    }

    template<TypeKind Kind, int size>
    i64 DuringType<Kind, size>::getInitValue() const {
        return 0;
    }

    template<TypeKind Kind, int size>
    int DuringType<Kind, size>::getNBits() const {
        return DuringType<Kind, size>::nbits;
    }
}




#endif //PLC2LLVM_DURINGTYPE_H
